import { Readable } from "stream";

export abstract class Initializable {
    abstract isInitialized(): boolean;
    awaitInitialized() {
        return new Promise<void>((resolve, reject) => {
            const timerID = setInterval(() => {
                if (this.isInitialized()) {
                    clearInterval(timerID);
                    resolve();
                }
            }, 100);
        });
    };
}

export namespace db {
    export interface DBError extends Error {
        /** 错误码 */
        errCode: number;
        /** 错误信息 */
        message: string;
    }
    /** 可遍历结果集 */
    export interface ResultSet<T> {
        /** 关闭结果集 */
        close(): Promise<void>;
        close(cb: (error: DBError) => void): void;
        /** 获取结果集下一行 */
        getRow(): Promise<T>;
        getRow(cb: (error: DBError, row: T) => void): void;
        /** 获取结果集下N行 */
        getRows(numRows: number): Promise<T[]>;
        getRows(numRows: number, cb: (error: DBError, row: T[]) => void): void;
        /** 获取结果集总行数 */
        getRowCount(): Promise<number>;
        getRowCount(cb: (error: DBError, rowCount: number) => void): void;
        /**
         * 将结果集转换成可读流
         * 流的data事件，可获取到结果集中的一行
         * 流的metadata事件，可获取到执行元信息dmdb.Metadata
         * 流的end事件，表示结果集已读取完毕
         */
        toQueryStream(): Readable;
    }
    /** 语句执行结果 */
    export interface Result<T> {
        /** 查询语句的结果集，当执行选项中的resultSet为true时才会存在 */
        resultSet?: ResultSet<T>;
        /** 查询语句的所有结果行，当执行选项中的resultSet为false时才会存在 */
        rows?: T[];
        /** DML语句影响行数。对于非DML语句(如select、PL/SQL)，此字段为undefined */
        rowsAffected?: number;
    }

    interface BindParameter {
        /** 
         * 绑定方向,取值：BIND_IN / BIND_INOUT / BIND_OUT 
         * @default BIND_IN
         */
        dir?: number;
        /** 参数绑入时的值 */
        val?: any;
        /**
         * 绑定参数的数据类型，取值范围是dmdb中的数据类型，如
         * dmdb.NUMBER，dmdb.STRING等
         */
        type?: number;
    }
    export type BindParameters = Record<string, BindParameter | string | number | Date | Buffer | null | undefined> | BindParameter[] | any[];

    export interface IConnection {

        /**
         * 连接是否连通
         */
        ping(): Promise<void>;
        ping(cb: (error: DBError) => void): void;

        /**
         * 执行SQL或PL/SQL语句
         * @param sql SQL or PL/SQL statement
         * @param bindParams 绑定参数
         */
        execute<T>(sql: string, bindParams?: BindParameters): Promise<Result<T>>;
        execute<T>(sql: string, bindParams?: BindParameters, resultSet?: boolean): Promise<Result<T>>;
        /**
         * 执行SQL或PL/SQL语句
         * @param sql SQL or PL/SQL statement
         */
        execute<T>(sql: string): Promise<Result<T>>;

        /** 提交当前事务 */
        commit(): Promise<void>;
        commit(cb: (error: DBError) => void): void;
        /** 回滚当前事务 */
        rollback(): Promise<void>;
        rollback(cb: (error: DBError) => void): void;

        /** 关闭连接 */
        close(): Promise<void>;
    }
    export interface IConnectionConstructor<T> {
        new (connection: T): IConnection;
        new (url: string, username: string, password: string): IConnection;
    }

    export interface IPool {
        
        /**
         * 关闭连接池
         * @param drainTime 池中连接被关闭的最大等待时间
         */
        close(drainTime?: number): Promise<void>;
        close(drainTime: number, cb: (error: DBError) => void): void;

        /**
         * 从连接池中获取一个空闲的连接；
         * 如果没有空闲的连接，则会新建连接
         */
        getConnection(): Promise<IConnection>;
        getConnection(cb: (error: DBError, connection: IConnection) => void): void;
    }
    export interface IPoolConstructor<T> {
        new (pool: T): IPool;
        new (url: string, username: string, password: string): IPool;
    }
}


export interface IDB {
    ping(): Promise<void>;
    execute<T>(sql: string, params?: db.BindParameters): Promise<db.Result<T>>;
    execute<T>(sql: string, params?: db.BindParameters, resultSet?: boolean): Promise<db.Result<T>>;
    commit(): Promise<void>;
    rollback(): Promise<void>;
    setSchema(schemaName: string): Promise<void>;
    getSchemaNames(): Promise<string[]>;
    getTableNames(schemaName: string): Promise<string[]>;
    getTableDDL(schemaName: string, tableName: string): Promise<string>;
    close(): Promise<void>;
}

export abstract class DB extends Initializable implements IDB {
    pool: db.IPool;
    connection?: db.IConnection;

    /**
     * 创建一个数据库连接实例
     * @param pool 数据库连接池
     */
    constructor(pool: db.IPool) {
        super();
        this.pool = pool;
        this.getConnection();
    }

    abstract setSchema(schemaName: string): Promise<void>;
    abstract getSchemaNames(): Promise<string[]>;
    abstract getTableNames(schemaName: string): Promise<string[]>;
    abstract getTableDDL(schemaName: string, tableName: string): Promise<string>;

    isInitialized(): boolean {
        return this.connection !== undefined;
    }
    
    ping(): Promise<void> {
        return new Promise(async (resolve, reject) => {
            if (!this.connection) {
                await this.getConnection();
            }
            if (!this.connection) {
                reject(new Error("Connection is not initialized."));
                return;
            }
            this.connection.ping();
            resolve();
        });
    }

    async getConnection(): Promise<db.IConnection> {
        this.connection ??= await this.pool.getConnection();
        if (!this.connection) {
            throw new Error("Connection initialization failed.");
        }
        return this.connection;
    }

    async execute<T>(sql: string, params?: db.BindParameters, resultSet?: boolean): Promise<db.Result<T>> {
        this.connection ??= await this.getConnection();
        try {
            return await this.connection.execute<T>(sql, params || [], resultSet || false);
        } catch (err: any) {
            throw new Error("execute error: " + err.message);
        }
    }
    
    commit(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.commit();
    }
    rollback(): Promise<void> {
        if (!this.connection) {
            throw new Error("Connection is not initialized.");
        }
        return this.connection.rollback();
    }

    close(): Promise<void> {
        if (!this.pool) {
            throw new Error("Connection pool is not initialized.");
        }
        return this.pool.close();
    }
}



export default db;
